package org.apache.ibatis.common.type;

import org.apache.ibatis.common.io.Resources;
import org.apache.ibatis.config.binding.MapperMethod;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.*;
import java.time.chrono.JapaneseDate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Name TypeHandlerRegistry
 * @Description: 通用类型处理器注册表
 * 描述信息：该类是一个类型处理器注册表，维护者数据类型（JDBC 数据类型 -> Java 数据类型）和相关处理器的对应关系。
 * Mybatis 在遇到某种类型的数据时，可以通过该注册表快速找到与之对应的类型处理器。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-09 18:22:09
 **/
public final class TypeHandlerRegistry {
    // 所有类型处理器字典 <类型处理器 Class 类, 类型处理器>
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
    // 默认枚举类型处理器
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    // JDBC 类型与对应的类型处理器的映射字典
    private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
    // JAVA 类型与 Map<JdbcType, TypeHandler<?>> 的映射字典
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
    // JAVA 类型与空的 Map<JdbcType, TypeHandler<?>> 的映射字典，表示该 JAVA 类型没有对应的  Map<JdbcType, TypeHandler<?>>。
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // 未知类型处理器
    private final TypeHandler<Object> unknownTypeHandler = new UnkownTypeHandler(this);

    public TypeHandlerRegistry() {
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINIINT, new ByteTypeHandler());

        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());

        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());

        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());

        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());

        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());

        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());

        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());

        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new StringTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());

        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
        register(Instant.class, new InstantTypeHandler());
        register(LocalDate.class, new LocalDateTypeHandler());
        register(JapaneseDate.class, new JapanseDateTypeHandler());
        register(LocalTime.class, new LocalTimeTypeHandler());
        register(OffsetTime.class, new OffsetTimeTypeHandler());
        register(LocalDateTime.class, new LocalDateTimeTypeHandler());
        register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
        register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
        register(Year.class, new YearTypeHandler());
        register(Month.class, new MonthTypeHandler());
        register(YearMonth.class, new YearMonthTypeHandler());
        register(String.class, JdbcType.SQLXML, new SqlXmlTypeHandler());

        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
        register(InputStream.class, new BlogByteObjectArrayTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlogByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());

        register(Object.class, unknownTypeHandler);
        register(JdbcType.OTHER, unknownTypeHandler);
        register(Object.class, JdbcType.OTHER, unknownTypeHandler);
    }

    public void register(String packageName) {
        // TODO 待实现，需要 io 包中的 ResolverUtil 工具类
    }

    public <T> void register(TypeHandler<T> typeHandler) {
        boolean mappedTypeFound = false;
        MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
        if (mappedTypes != null) {
            for (Class<?> handledType : mappedTypes.value()) {
                register(handledType, typeHandler);
                mappedTypeFound = true;
            }
        }
        if (!mappedTypeFound && typeHandler instanceof TypeReference) {
            try {
                TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
                register(typeReference.getRawType(), typeHandler);
                mappedTypeFound = true;
            } catch (Throwable e) {
                // nothing to do...
            }
        }
        if (!mappedTypeFound) register((Class<T>) null, typeHandler);
    }

    public void register(Class<?> typeHandlerClass) {
        boolean mappedTypeFound = false;
        MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
        if (mappedTypes != null) {
            for (Class<?> javaTypeClass : mappedTypes.value()) {
                register(javaTypeClass, typeHandlerClass);
                mappedTypeFound = true;
            }
        }
        if (!mappedTypeFound) register(getInstance(null, typeHandlerClass));
    }

    public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
        register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
    }

    public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
        register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
    }

    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
            for (JdbcType jdbcType : mappedJdbcTypes.value()) register(javaType, jdbcType, typeHandler);
            if (mappedJdbcTypes.includeNullJdbcType()) register(javaType, null, typeHandler);
        } else register(javaType, null, typeHandler);
    }

    public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> typeHandler) {
        register(javaTypeReference.getRawType(), typeHandler);
    }

    private <T> void register(JdbcType jdbcType, TypeHandler<?> typeHandler) {
        jdbcTypeHandlerMap.put(jdbcType, typeHandler);
    }

    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> typeHandler) {
        register((Type) type, jdbcType, typeHandler);
    }

    private <T> void register(Type javaType, JdbcType jdbcType, TypeHandler<? extends T> typeHandler) {
        if (javaType != null) {
            Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
            if (map == null || map == NULL_TYPE_HANDLER_MAP) {
                map = new HashMap<>();
                typeHandlerMap.put(javaType, map);
            }
            map.put(jdbcType, typeHandler);
        }
        allTypeHandlersMap.put(typeHandler.getClass(), typeHandler);
    }

    public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
        if (javaTypeClass != null) {
            try {
                Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
                return (TypeHandler<T>) c.newInstance(javaTypeClass);
            } catch (Exception e) {
                String errorMsg = "无法调用类型处理器 [" + typeHandlerClass + "] 的构造器。";
                throw new TypeException(errorMsg, e);
            }
        }
        try {
            Constructor<?> c = typeHandlerClass.getConstructor();
            return (TypeHandler<T>) c.newInstance();
        } catch (Exception e) {
            String errorMsg = "类型处理器 [ " + typeHandlerClass + "] 中没有发现一个可以使用的构造器。";
            throw new TypeException(errorMsg, e);
        }
    }

    public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
        this.defaultEnumTypeHandler = defaultEnumTypeHandler;
    }

    public Collection<TypeHandler<?>> getTypeHandlers() {
        return Collections.unmodifiableCollection(allTypeHandlersMap.values());
    }

    public TypeHandler<Object> getUnknownTypeHandler() {
        return unknownTypeHandler;
    }

    public boolean hasTypeHandler(Class<?> javaType) {
        return hasTypeHandler(javaType, null);
    }

    public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
        return hasTypeHandler(javaTypeReference, null);
    }

    public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
        return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
    }

    public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
        return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
    }

    public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
        return allTypeHandlersMap.get(handlerType);
    }

    public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
        return getTypeHandler((Type) type, null);
    }

    public TypeHandler getTypeHandler(JdbcType jdbcType) {
        return jdbcTypeHandlerMap.get(jdbcType);
    }

    public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
        return getTypeHandler(javaTypeReference, null);
    }

    public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
        return getTypeHandler((Type) type, jdbcType);
    }

    public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) {
        return getTypeHandler(javaTypeReference.getRawType(), jdbcType);
    }

    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        // 如果是 ParamMap，不是单一的 JAVA 类型，直接返回 null
        if (MapperMethod.ParamMap.class.equals(type)) return null;
        // 先根据 JAVA 类型找到对应的 JdbcTypeHandlerMap
        Map<JdbcType, TypeHandler<?>> map = getJdbcTypeHandlerMap(type);
        TypeHandler<?> typeHandler = null;
        // 如果存在 JdbcTypeHandlerMap
        if (map != null) {
            // 根据 JDBC 类型寻找对应的处理器
            typeHandler = map.get(jdbcType);
            if (typeHandler == null) typeHandler = map.get(null);
            // 如果 JdbcTypeHandlerMap 只有一个类型处理器，则作为缺省值
            if (typeHandler == null) typeHandler = pickSoleHandler(map);
        }
        return (TypeHandler<T>) typeHandler;
    }

    private Map<JdbcType, TypeHandler<?>> getJdbcTypeHandlerMap(Type type) {
        // 1. 根据 JAVA 类型找到对应的 JdbcTypeHandlerMap
        Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = typeHandlerMap.get(type);
        // 1.1 JdbcTypeHandlerMap 为空，则肯定没有符合的类型处理器
        if (NULL_TYPE_HANDLER_MAP.equals(jdbcTypeHandlerMap)) return null;
        // 1.2 未找到对应的 JdbcTypeHandlerMap
        if (jdbcTypeHandlerMap == null && type instanceof Class) {
            Class<?> clazz = (Class<?>) type;
            // 如果 clazz 是枚举类，则寻找枚举类的处理器
            if (Enum.class.isAssignableFrom(clazz)) {
                Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
                jdbcTypeHandlerMap = getJdbcTypeHandlerMapForEnumInterfaces(enumClass, enumClass);
                // 未找到枚举类的处理器，则注册一个默认处理器并返回
                if (jdbcTypeHandlerMap == null) {
                    register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
                    return typeHandlerMap.get(enumClass);
                }
            } else {
                // 否则，寻找父类的处理器
                jdbcTypeHandlerMap = getJdbcTypeHandlerMapForSuperclass(clazz);
            }
        }
        // 2. 将 JdbcTypeHandlerMap 注册到 TypeHandlerMap
        typeHandlerMap.put(type, jdbcTypeHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcTypeHandlerMap);
        return jdbcTypeHandlerMap;
    }

    private Map<JdbcType, TypeHandler<?>> getJdbcTypeHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClass) {
        for (Class<?> iface : clazz.getInterfaces()) {
            Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = typeHandlerMap.get(iface);
            if (jdbcTypeHandlerMap == null)
                jdbcTypeHandlerMap = getJdbcTypeHandlerMapForEnumInterfaces(iface, enumClass);
            if (jdbcTypeHandlerMap != null) {
                Map<JdbcType, TypeHandler<?>> map = new HashMap<>();
                for (Map.Entry<JdbcType, TypeHandler<?>> entry : jdbcTypeHandlerMap.entrySet()) {
                    map.put(entry.getKey(), getInstance(enumClass, entry.getValue().getClass()));
                }
                return map;
            }
        }
        return null;
    }

    private Map<JdbcType, TypeHandler<?>> getJdbcTypeHandlerMapForSuperclass(Class<?> clazz) {
        Class<?> superclass = clazz.getSuperclass();
        if (superclass == null || Object.class.equals(superclass)) return null;
        Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = typeHandlerMap.get(superclass);
        if (jdbcTypeHandlerMap != null) return jdbcTypeHandlerMap;
        else return getJdbcTypeHandlerMapForSuperclass(superclass);
    }

    private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap) {
        TypeHandler<?> soleHandler = null;
        for (TypeHandler<?> handler : jdbcTypeHandlerMap.values()) {
            if (soleHandler == null) soleHandler = handler;
            else if (!handler.getClass().equals(soleHandler.getClass())) return null;
        }
        return soleHandler;
    }

}

